package skeins

const (
    // hash size
    Size224 = 28
    Size256 = 32

    BlockSize256 = 32
)

// digest256 represents the partial evaluation of a checksum.
type digest256 struct {
    s   [4]uint64
    x   [BlockSize256]byte
    nx  int
    len uint64

    bcount uint64

    hs      int
    initVal [4]uint64
}

// newDigest256 returns a new hash.Hash computing the checksum
func newDigest256(hs int, initVal [4]uint64) *digest256 {
    d := new(digest256)
    d.hs = hs
    d.initVal = initVal
    d.Reset()

    return d
}

func (d *digest256) Reset() {
    copy(d.s[:], d.initVal[:])

    d.x = [BlockSize256]byte{}
    d.nx = 0
    d.len = 0

    d.bcount = 0
}

func (d *digest256) Size() int {
    return d.hs
}

func (d *digest256) BlockSize() int {
    return BlockSize256
}

func (d *digest256) Write(p []byte) (nn int, err error) {
    nn = len(p)

    d.len += uint64(nn)

    var etype int

    plen := len(p)
    for d.nx + plen >= BlockSize256 {
        copy(d.x[d.nx:], p)

        if d.bcount == 0 {
            etype = 224
        } else {
            etype = 96
        }
        d.bcount++

        d.ubi(etype, 0)

        xx := BlockSize256 - d.nx
        plen -= xx

        p = p[xx:]
        d.nx = 0
    }

    copy(d.x[d.nx:], p)
    d.nx += plen

    return
}

func (d *digest256) Sum(in []byte) []byte {
    // Make a copy of d so that caller can keep writing and summing.
    d0 := *d
    hash := d0.checkSum()
    return append(in, hash...)
}

func (d *digest256) checkSum() (out []byte) {
    ptr := d.nx

    for i := ptr; i < BlockSize256; i++ {
        d.x[i] = 0x00
    }

    if d.bcount == 0 {
        d.ubi(480, ptr)
    } else {
        d.ubi(352, ptr)
    }

    for i := 0; i < BlockSize256; i++ {
        d.x[i] = 0x00
    }

    d.bcount = 0
    d.ubi(510, 8)

    buf := make([]byte, BlockSize256)
    for i := 0; i < 4; i++ {
        putu64(buf[8*i:], d.s[i])
    }

    out = make([]byte, d.hs)
    copy(out, buf)

    return
}

func (d *digest256) ubi(etype int, extra int) {
    var m0 = getu64(d.x[0:])
    var m1 = getu64(d.x[8:])
    var m2 = getu64(d.x[16:])
    var m3 = getu64(d.x[24:])

    h0 := d.s[0]
    h1 := d.s[1]
    h2 := d.s[2]
    h3 := d.s[3]

    var p0 = m0
    var p1 = m1
    var p2 = m2
    var p3 = m3

    var h4 = (h0 ^ h1) ^ (h2 ^ h3) ^ 0x1BD11BDAA9FC1A22
    var t0 = (d.bcount << 5) + uint64(extra)
    var t1 = (d.bcount >> 59) + (uint64(etype) << 55)
    var t2 = t0 ^ t1

    p0 += h0
    p1 += h1 + t0
    p2 += h2 + t1
    p3 += h3 + 0
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h1
    p1 += h2 + t1
    p2 += h3 + t2
    p3 += h4 + 1
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h2
    p1 += h3 + t2
    p2 += h4 + t0
    p3 += h0 + 2
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h3
    p1 += h4 + t0
    p2 += h0 + t1
    p3 += h1 + 3
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h4
    p1 += h0 + t1
    p2 += h1 + t2
    p3 += h2 + 4
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h0
    p1 += h1 + t2
    p2 += h2 + t0
    p3 += h3 + 5
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h1
    p1 += h2 + t0
    p2 += h3 + t1
    p3 += h4 + 6
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h2
    p1 += h3 + t1
    p2 += h4 + t2
    p3 += h0 + 7
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h3
    p1 += h4 + t2
    p2 += h0 + t0
    p3 += h1 + 8
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h4
    p1 += h0 + t0
    p2 += h1 + t1
    p3 += h2 + 9
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h0
    p1 += h1 + t1
    p2 += h2 + t2
    p3 += h3 + 10
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h1
    p1 += h2 + t2
    p2 += h3 + t0
    p3 += h4 + 11
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h2
    p1 += h3 + t0
    p2 += h4 + t1
    p3 += h0 + 12
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h3
    p1 += h4 + t1
    p2 += h0 + t2
    p3 += h1 + 13
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h4
    p1 += h0 + t2
    p2 += h1 + t0
    p3 += h2 + 14
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h0
    p1 += h1 + t0
    p2 += h2 + t1
    p3 += h3 + 15
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h1
    p1 += h2 + t1
    p2 += h3 + t2
    p3 += h4 + 16
    p0 += p1
    p1 = (p1 << 14) ^ (p1 >> (64 - 14)) ^ p0
    p2 += p3
    p3 = (p3 << 16) ^ (p3 >> (64 - 16)) ^ p2
    p0 += p3
    p3 = (p3 << 52) ^ (p3 >> (64 - 52)) ^ p0
    p2 += p1
    p1 = (p1 << 57) ^ (p1 >> (64 - 57)) ^ p2
    p0 += p1
    p1 = (p1 << 23) ^ (p1 >> (64 - 23)) ^ p0
    p2 += p3
    p3 = (p3 << 40) ^ (p3 >> (64 - 40)) ^ p2
    p0 += p3
    p3 = (p3 << 5) ^ (p3 >> (64 - 5)) ^ p0
    p2 += p1
    p1 = (p1 << 37) ^ (p1 >> (64 - 37)) ^ p2

    p0 += h2
    p1 += h3 + t2
    p2 += h4 + t0
    p3 += h0 + 17
    p0 += p1
    p1 = (p1 << 25) ^ (p1 >> (64 - 25)) ^ p0
    p2 += p3
    p3 = (p3 << 33) ^ (p3 >> (64 - 33)) ^ p2
    p0 += p3
    p3 = (p3 << 46) ^ (p3 >> (64 - 46)) ^ p0
    p2 += p1
    p1 = (p1 << 12) ^ (p1 >> (64 - 12)) ^ p2
    p0 += p1
    p1 = (p1 << 58) ^ (p1 >> (64 - 58)) ^ p0
    p2 += p3
    p3 = (p3 << 22) ^ (p3 >> (64 - 22)) ^ p2
    p0 += p3
    p3 = (p3 << 32) ^ (p3 >> (64 - 32)) ^ p0
    p2 += p1
    p1 = (p1 << 32) ^ (p1 >> (64 - 32)) ^ p2

    p0 += h3
    p1 += h4 + t0
    p2 += h0 + t1
    p3 += h1 + 18

    d.s[0] = m0 ^ p0
    d.s[1] = m1 ^ p1
    d.s[2] = m2 ^ p2
    d.s[3] = m3 ^ p3
}

